#!/usr/bin/env python
#-*- coding: UTF-8 -*-
# appif.py: task manager interface functions for application
#
# Copyright (C) 2008-2010 Hily Jiang <hilyjiang@gmail.com>
#
# This file is part of dtask.
#
# This software is provided 'as-is', without any express or implied
# warranty.  In no event will the authors be held liable for any damages
# arising from the use of this software.
#
# Permission is granted to anyone to use this software for any purpose,
# including commercial applications, and to alter it and redistribute it
# freely, subject to the following restrictions:
#
# 1. The origin of this software must not be misrepresented; you must not
#    claim that you wrote the original software. If you use this software
#    in a product, an acknowledgment in the product documentation would be
#    appreciated but is not required.
# 2. Altered source versions must be plainly marked as such, and must not be
#    misrepresented as being the original software.
# 3. This notice may not be removed or altered from any source distribution.

"""
Task Manager interface functions for Application.

Application <-> Task Manager Protocol:

* New Connection
  (A -> T) Send data format:    PASSPORT appName appKey\n

* New Task
  (A -> T) Send data format:    TASK taskType taskTimeout taskParams\n
  (T -> A) Return data format:  TASKID taskId\n

* Get Result
  (A -> T) Send data format:    GETRESULT taskId fullInfo\n
                                set fullInfo=1 to get full result info
  (T -> A) Return data format:  RESULT taskId resultJson\n
"""

import sys
import threading
import cjson

from utils import CommandBuffer


CFG_APP_BUFSIZ = 1024


class AppIfBuffer(CommandBuffer):
    """Application buffer to receive command string and application info."""

    def __init__(self):
        CommandBuffer.__init__(self)
        self.appName = ''
        self.appKey = ''
        self.hasInfo = False

    def setInfo(self, appName='', appKey=''):
        """Set application info."""
        if self.hasInfo:
            return False
        self.appName = appName
        self.appKey = appKey
        self.hasInfo = True

    def getInfo(self):
        """Get application info."""
        return (self.appName, self.appKey)


def appGetResultThreadFunc(taskId, fullInfo, taskManager,
                           appClientSock, appClientName):
    """Thread function to send result to application."""
    appIp, appPort = appClientName
    result = taskManager.getResult(taskId, fullInfo, appIp, appPort)
    if result == None: return
    resultJson = cjson.encode(result)
    try:
        appClientSock.sendall('RESULT %d %s\n' % (taskId, resultJson))
    except Exception, e:
        print e
        pass


def appCommandHandler(cmdInfo, taskManager, aiBuf,
                      appClientSock, appClientName):
    """Handle the application's commands."""
    cmd, params = cmdInfo

    print appClientName, '[APP]', cmd, 
    if params:
        print params[:40]
    else:
        print

    if cmd == 'TASK':
        appip, appport = appClientName

        try:
            taskType, taskTimeout, taskParams = params.split(None, 2)
        except Exception, e:
            print appClientName, '[APP]', sys._getframe().f_lineno, e
        else:
            taskId = taskManager.newTask(appip, appport, taskType, taskTimeout,
                                         taskParams, aiBuf.getInfo()[0])
            # return task ID to application
            appClientSock.sendall('TASKID %d\n' % taskId)
    elif cmd == 'GETRESULT':
        try:
            taskId, fullInfo = params.split(None, 1)
            taskId = int(taskId)
            fullInfo = int(fullInfo)
        except ValueError:
            print appClientName, '[APP] invalid params', params
            appClientSock.sendall('RESULT %d \n' % taskId)
        else:
            # start a new thread, or this thread may block to
            # and wait for the result's return
            t = threading.Thread(target=appGetResultThreadFunc,
                                 args=(taskId, fullInfo, taskManager,
                                       appClientSock, appClientName))
            t.setDaemon(True)
            t.start()
    elif cmd == 'PASSPORT':
        params = params.split(None, 1)
        if len(params) == 1:
            appName = params[0]
            appKey = ''
        else:
            appName, appKey = params
        aiBuf.setInfo(appName, appKey)
        # todo: authorization


def appRecvData(taskManager, aiBuf, appClientSock, appClientName, data):
    """Process data send by application."""
    aiBuf.addData(data)

    while True:
        cmdInfo = aiBuf.getCommand()
        if cmdInfo:
            appCommandHandler(cmdInfo, taskManager, aiBuf,
                              appClientSock, appClientName)
        else:
            break


def appRecvThreadFunc(taskManager, appClientSock, appClientSocks):
    """Thread function for each application connection to process data."""
    appClientName = appClientSock.getpeername()
    aiBuf = AppIfBuffer()

    while True:
        try:
            data = appClientSock.recv(CFG_APP_BUFSIZ)
            if data == '': break
            appRecvData(taskManager, aiBuf, appClientSock, appClientName, data)
        except Exception, e:
            print appClientName, '[APP]', sys._getframe().f_lineno, e
    
    print appClientName, '[APP] quit'
    appClientSock.close()
    appClientSocks.remove(appClientSock)


def startApplicationIf(taskManager, appServerSock, appClientSocks):
    """Start interface for applications."""
    appRecvThreads = []

    while True:
        appClientSock, appClientAddr = appServerSock.accept()
        appClientSocks.append(appClientSock)
        print appClientAddr, '[APP] connected'
        t = threading.Thread(target=appRecvThreadFunc,
                   args=(taskManager, appClientSock, appClientSocks))
        t.setDaemon(True)
        appRecvThreads.append(t)
        t.start()

    print 'Waiting for application clients to release...',
    for t in appRecvThreads:
        t.join()
    print 'ok'
